#include "engine.h"
#include "core/stringhelper.h"

#include "3rdparty/assimp/include/assimp.h"
#include "3rdparty/assimp/include/aiPostProcess.h"
#include "3rdparty/assimp/include/aiMesh.h"
#include "3rdparty/assimp/include/aiScene.h"
#include "3rdparty/assimp/include/aiMaterial.h"
#include "core/log.h"

#include "render/rendersystem.h"
#include "assimploader.h"

#include <windows.h>

string act_path;
int load_assimp_node(const aiScene* scene, aiNode* ainode,vector<rendernode_t>& node);

#if 1
void load_animation(vector<animation_t>& anim, const vector<rendernode_t> node_buf, const aiScene* scene)
{
	if (scene->HasAnimations())
	{
		for (uint32 i=0;i<scene->mNumAnimations;i++)
		{
			aiAnimation* aiclip=scene->mAnimations[i];

			stringhash animname=stringhash::get(aiclip->mName.data);
			if (!animname.value)
				animname="autoloop-anim";
			else
			{
				volatile int dejo=1;
			}

			{
				anim.push_back(animation_t());

				animation_t* clip=&anim.back();

				float max_time=0;
				int max_key=0;
				for (uint32 j=0;j<aiclip->mNumChannels;j++)
				{
					aiNodeAnim* aitrack=aiclip->mChannels[j];
					int nodeindex=get_nodeindex(node_buf,stringhash::get(aitrack->mNodeName.data));
					if (nodeindex!=-1 && (aitrack->mNumPositionKeys>1 || aitrack->mNumRotationKeys>1 || aitrack->mNumScalingKeys>1))
					{
						const rendernode_t* node=&node_buf[nodeindex];
						clip->tracks.push_back(animtrack_t());
						animtrack_t& track=clip->tracks.back();
						track.node_index=nodeindex;

						float worst_pos_distance=0;
						track.positions.resize(aitrack->mNumPositionKeys);
						for (uint32 k=0;k<aitrack->mNumPositionKeys;k++)
						{
							track.positions[k]=*((float3*)&aitrack->mPositionKeys[k].mValue);
							if (max_time<aitrack->mPositionKeys[k].mTime)
							{
								max_time=(float)aitrack->mPositionKeys[k].mTime; max_key=k;
							}

							float pos_distance=dot(track.positions[0]-track.positions[k], track.positions[0]-track.positions[k]);
							worst_pos_distance=max(worst_pos_distance, pos_distance);
						}
						if (worst_pos_distance<0.00001f)
							track.positions.resize(1);

						float worst_rot_distance=0;
						track.rotations.resize(aitrack->mNumRotationKeys);
						for (uint32 k=0;k<aitrack->mNumRotationKeys;k++)
						{
							// ne ird at copyra, mert az assim quat wxyz!
							track.rotations[k].x=aitrack->mRotationKeys[k].mValue.x;
							track.rotations[k].y=aitrack->mRotationKeys[k].mValue.y;
							track.rotations[k].z=aitrack->mRotationKeys[k].mValue.z;
							track.rotations[k].w=aitrack->mRotationKeys[k].mValue.w;
							if (max_time<aitrack->mRotationKeys[k].mTime)
							{
								max_time=(float)aitrack->mRotationKeys[k].mTime;
								max_key=k;
							}
							float rot_distance=dot(track.rotations[0]-track.rotations[k], track.rotations[0]-track.rotations[k]);
							worst_rot_distance=max(worst_rot_distance, rot_distance);
						}
						if (worst_rot_distance<0.00001f)
							track.rotations.resize(1);

						float worst_scale_distance=0;
						track.scales.resize(aitrack->mNumScalingKeys);
						for (uint32 k=0;k<aitrack->mNumScalingKeys;k++)
						{
							track.scales[k]=*((float3*)&aitrack->mScalingKeys[k].mValue);
							if (max_time<aitrack->mScalingKeys[k].mTime)
							{
								max_time=(float)aitrack->mScalingKeys[k].mTime;
								max_key=k;
							}
							float scale_distance=dot(track.scales[0]-track.scales[k], track.scales[0]-track.scales[k]);
							worst_scale_distance=max(worst_scale_distance, scale_distance);
						}
						if (worst_scale_distance<0.00001f)
							track.scales.resize(1);

					}
				}

				if (clip->tracks.size())
				{
					if (aiclip->mTicksPerSecond>0) max_time/=(float)aiclip->mTicksPerSecond;
					clip->timescale=(float)max_key/max_time;
					clip->length=max_time;
					clip->name=animname;
				}
				else 
				{
					anim.pop_back();
				}
			}
		}
	}
}
#endif

bool load_object(vector<rendernode_t>&node,vector<animation_t>& anim, const char* filename)
{
//	Assimp::Importer importer;
	const aiScene* scene =  aiImportFile(filename,
		aiProcessPreset_TargetRealtime_MaxQuality |
		aiProcess_ConvertToLeftHanded|
		aiProcess_TransformUVCoords|
		aiProcess_FindDegenerates);

	if(!scene)
	{
		log_t::log(_str("\n%s\n\n",aiGetErrorString()));
		ASSERTTXT(0,_str("dae file could not be loaded: %s",filename));
		return false;
	}

	act_path=stringhelper::get_path(filename);
//	node.reserve(scene->no)

	load_assimp_node(scene,scene->mRootNode,node);

	load_animation(anim,node,scene);

	aiReleaseImport( scene);
	return true;
}



void CalculateTangent(vector<float4>& tangent, aiMesh* mesh)
{
	if (!mesh->HasTextureCoords(0) || !mesh->HasNormals())
		return;
	tangent.resize(mesh->mNumVertices);
	vector<double3> tan1(mesh->mNumVertices, double3(0,0,0));
	vector<double3> tan2(mesh->mNumVertices, double3(0,0,0));

	uint32 numtri=mesh->mNumFaces;// indices.size()/3;
//	const uint16* tri=&indices[0];
	for (uint32 i=0;i<numtri;i++)
	{
		uint32 i1=mesh->mFaces[i].mIndices[0];
		uint32 i2=mesh->mFaces[i].mIndices[1];
		uint32 i3=mesh->mFaces[i].mIndices[2];
		double3 v1(mesh->mVertices[i1].x, mesh->mVertices[i1].y, mesh->mVertices[i1].z);
		double3 v2(mesh->mVertices[i2].x, mesh->mVertices[i2].y, mesh->mVertices[i2].z);
		double3 v3(mesh->mVertices[i3].x, mesh->mVertices[i3].y, mesh->mVertices[i3].z);

		double3 w1(mesh->mTextureCoords[0][i1].x, mesh->mTextureCoords[0][i1].y, 0);
		double3 w2(mesh->mTextureCoords[0][i2].x, mesh->mTextureCoords[0][i2].y, 0);
		double3 w3(mesh->mTextureCoords[0][i3].x, mesh->mTextureCoords[0][i3].y, 0);

		double x1 = v2.x - v1.x;
		double x2 = v3.x - v1.x;
		double y1 = v2.y - v1.y;
		double y2 = v3.y - v1.y;
		double z1 = v2.z - v1.z;
		double z2 = v3.z - v1.z;

		double s1 = w2.x - w1.x;
		double s2 = w3.x - w1.x;
		double t1 = w2.y - w1.y;
		double t2 = w3.y - w1.y;

		double d = (s1 * t2 - s2 * t1);

		// kiszedve! majd lefagy, ha szar.
//		if (abs(d)>0.0000001)
//		{
			double r = 1.0 / d;

			double3 sdir((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r,
				(t2 * z1 - t1 * z2) * r);
			double3 tdir((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r,
				(s1 * z2 - s2 * z1) * r);

			tan1[i1] += sdir;
			tan1[i2] += sdir;
			tan1[i3] += sdir;

			tan2[i1] += tdir;
			tan2[i2] += tdir;
			tan2[i3] += tdir;
//		} else {
//			volatile int abcd=1;
//		}
	}


	for (uint32 i=0;i<mesh->mNumVertices;i++)
	{
		double3 n(mesh->mNormals[i].x, mesh->mNormals[i].y, mesh->mNormals[i].z);
		const double3& t = tan1[i];

		// Calculate handedness
		float eredsign = (dot(cross(n,t),tan2[i])) < 0.0f ? -1.0f : 1.0f;

		double3 eredmeny=t-(n*dot(n, t));
		eredmeny.normalize();
		tangent[i].set((float)eredmeny.x, (float)eredmeny.y, (float)eredmeny.z, eredsign);
	}
}



int load_assimp_node(const aiScene* scene,aiNode* ainode,vector<rendernode_t>& nodebuf)
{
#if 0
}
#else
	aiMesh** rawmeshes = scene->mMeshes;
	aiMaterial** materials = scene->mMaterials;

	//temporary storage data
	UINT *meshid;
	UINT materialid;


	//#define OLD_FORMAT
#define AIMatToD3DX (float4x4&)
#define AIVec3ToD3DX (float3&)
#define AIVec3ToD3DX2 (float2&)

	uint32 index=nodebuf.size();
	nodebuf.resize(nodebuf.size()+1);
	rendernode_t* node=&nodebuf.back();

	node->local_matrix = AIMatToD3DX(ainode->mTransformation);
	node->local_matrix.transpose();
	node->name=stringhash::get(ainode->mName.data);

	//VERTICES
	meshid = ainode->mMeshes;
	if (ainode->mNumMeshes>0)
		node->mesh=new rendermesh_t();

	for (uint32 n=0; n<ainode->mNumMeshes; ++n)
	{
		aiMesh* actMesh=rawmeshes[meshid[n]];
		if (actMesh->mPrimitiveTypes!=aiPrimitiveType_TRIANGLE)
			continue;
		if ((!actMesh->mNumVertices) || (!actMesh->mNumFaces))
			continue;
		if (!actMesh->HasPositions())
			continue;

		materialid = actMesh->mMaterialIndex;
		aiMaterial* mat = materials[materialid];
		aiString matname;
		mat->Get(AI_MATKEY_NAME,matname);
		string mn(matname.data);

		unsigned s=mn.find('-');
		unsigned e=mn.rfind('-');

		if (s==string::npos || e==string::npos)
		{
			unsigned s=mn.find('_');
			unsigned e=mn.rfind('_');
			if (s==string::npos || e==string::npos)
				mn="default";
			else
				mn=mn.substr(s+1,e-s-1);
		}
		else
			mn=mn.substr(s+1,e-s-1);

		if (mn=="normaltile")
		{
//			ASSERTTXT(0,"NORMALTILE!!!!");
		}

		rendermaterial_t* m=rendersystem_t::get_material(stringhash::get(mn));
		if (!m)
			m=rendersystem_t::get_material("default");
		node->mesh->submesh.push_back(submesh_t());
		submesh_t& submesh=node->mesh->submesh.back();

		submesh.init(actMesh->mNumVertices,actMesh->mNumFaces*3,m);
		uint32 vdeclflag=m->vdecl->flag;
		vector<float4> tangent;
		if (vdeclflag & VDECL_TANGENT)
		{
			CalculateTangent(tangent,actMesh);
		}

		char* locked=(char*)submesh.vertexbuffer.lock();
		for(uint32 i = 0; i < actMesh->mNumVertices; ++i)
		{
			float3 pos=(float3&)actMesh->mVertices[i];
			{
				(float3&)*locked=pos;
				locked+=12;
			}


			if (vdeclflag & VDECL_NORMAL)
			if (actMesh->HasNormals())
			{
				float3& n=(float3&)*locked;
				float3 vertex_normal=(float3&)actMesh->mNormals[i];
				normalize(vertex_normal);
				n=vertex_normal;
				locked+=12;
			}
			else
			{
				float3& n=(float3&)*locked;
				float3 vertex_normal=(float3&)actMesh->mVertices[i];
				normalize(vertex_normal);
				n=vertex_normal;
				locked+=12;
			}

			if (vdeclflag & VDECL_TANGENT)
			{
				if (tangent.size())
				{
					((float4&)*locked).set(tangent[i].x, tangent[i].y, tangent[i].z, tangent[i].w);
					locked+=16;
				}
				else
				{
					((float4&)*locked).set(0,0,0,0);
					locked+=16;
				}
			}


			if (vdeclflag & VDECL_COLOR)
			if (actMesh->HasVertexColors(0))
			{
				float4& c=(float4&)*locked;
				c=(float4&)actMesh->mColors[0][i];
				locked+=16;
			}
			else
			{
				float4& c=(float4&)*locked;
				c.set(1,1,1,1);
				locked+=16;
			}


			/*
			//tangent

			if (tangent.size())
			{
				((color8u&)*locked).set(tangent[i].x*127.5f+127.5f, tangent[i].y*127.5f+127.5f, tangent[i].z*127.5f+127.5f, tangent[i].w*127.5f+127.5f);
				locked+=4;
			}
			else
			{
				((color8u&)*locked).set(0,0,0,0);
				locked+=4;
			}

*/
			if (vdeclflag & VDECL_UV)
			{
				float2 texcoord=actMesh->HasTextureCoords(0) ? (float2&)actMesh->mTextureCoords[0][i] : float2(0,0);
				memcpy(locked, &texcoord, sizeof(float2));
				locked+=8;
			}
		}
		submesh.vertexbuffer.unlock();

		uint16* indlock=(uint16*)submesh.indexbuffer.lock();
		for (uint32 i=0;i<actMesh->mNumFaces;i++)
		{
			UINT* indices = (UINT*)actMesh->mFaces[i].mIndices;
			*indlock++=indices[0];
			*indlock++=indices[1];
			*indlock++=indices[2];
		}
		submesh.indexbuffer.unlock();

		aiString szPath;

		if (actMesh->GetNumUVChannels())
		{
			//			string ext_file_name;
			//			stringhelper::remove_extension(ext_file_name, file_name);
			//			stringhelper::get_path(temp_path, file_name);
			//diffuse texture
			if(AI_SUCCESS == aiGetMaterialString(mat, AI_MATKEY_TEXTURE_DIFFUSE(0), &szPath))
			{
				string texture_file_name=stringhelper::remove_extension(stringhelper::get_filename(stringhelper::fixup(szPath.data)));
				rendertexture_t* texture=rendersystem_t::get_texture(stringhash::get(act_path+texture_file_name));
				submesh.textures.push_back(texture);
			}


			//specular texture
			if(AI_SUCCESS == aiGetMaterialString(mat, AI_MATKEY_TEXTURE_SPECULAR(0), &szPath))
			{
				string texture_file_name=stringhelper::remove_extension(stringhelper::get_filename(stringhelper::fixup(szPath.data)));
				rendertexture_t* texture=rendersystem_t::get_texture(stringhash::get(act_path+texture_file_name));
				submesh.textures.push_back(texture);
			}
			//opacity texture
			if(AI_SUCCESS == aiGetMaterialString(mat, AI_MATKEY_TEXTURE_OPACITY(0), &szPath))
			{
				string texture_file_name=stringhelper::remove_extension(stringhelper::get_filename(stringhelper::fixup(szPath.data)));
				rendertexture_t* texture=rendersystem_t::get_texture(stringhash::get(act_path+texture_file_name));
				submesh.textures.push_back(texture);
			}
			//emissive texture
			if(AI_SUCCESS == aiGetMaterialString(mat, AI_MATKEY_TEXTURE_EMISSIVE(0), &szPath))
			{
				string texture_file_name=stringhelper::remove_extension(stringhelper::get_filename(stringhelper::fixup(szPath.data)));
				rendertexture_t* texture=rendersystem_t::get_texture(stringhash::get(act_path+texture_file_name));
				submesh.textures.push_back(texture);
			}
			//shininess texture
			if(AI_SUCCESS == aiGetMaterialString(mat, AI_MATKEY_TEXTURE_SHININESS(0), &szPath))
			{
				string texture_file_name=stringhelper::remove_extension(stringhelper::get_filename(stringhelper::fixup(szPath.data)));
				rendertexture_t* texture=rendersystem_t::get_texture(stringhash::get(act_path+texture_file_name));
				submesh.textures.push_back(texture);
			}
			//light map texture
			if(AI_SUCCESS == aiGetMaterialString(mat, AI_MATKEY_TEXTURE_LIGHTMAP(0), &szPath))
			{
				string texture_file_name=stringhelper::remove_extension(stringhelper::get_filename(stringhelper::fixup(szPath.data)));
				rendertexture_t* texture=rendersystem_t::get_texture(stringhash::get(act_path+texture_file_name));
				submesh.textures.push_back(texture);
			}
			//normal / height map texture
			if(AI_SUCCESS == aiGetMaterialString(mat, AI_MATKEY_TEXTURE_NORMALS(0), &szPath))
			{
				string texture_file_name=stringhelper::remove_extension(stringhelper::get_filename(stringhelper::fixup(szPath.data)));
				rendertexture_t* texture=rendersystem_t::get_texture(stringhash::get(act_path+texture_file_name));
				submesh.textures.push_back(texture);
			}
			if(AI_SUCCESS == aiGetMaterialString(mat, AI_MATKEY_TEXTURE_HEIGHT(0), &szPath))
			{
				string texture_file_name=stringhelper::remove_extension(stringhelper::get_filename(stringhelper::fixup(szPath.data)));
				rendertexture_t* texture=rendersystem_t::get_texture(stringhash::get(act_path+texture_file_name));
				submesh.textures.push_back(texture);
			}

			if(AI_SUCCESS == aiGetMaterialString(mat, AI_MATKEY_TEXTURE_AMBIENT(0), &szPath))
			{
				string texture_file_name=stringhelper::remove_extension(stringhelper::get_filename(stringhelper::fixup(szPath.data)));
				rendertexture_t* texture=rendersystem_t::get_texture(stringhash::get(act_path+texture_file_name));
				submesh.textures.push_back(texture);
			}

			if(AI_SUCCESS == aiGetMaterialString(mat, AI_MATKEY_TEXTURE_DISPLACEMENT(0), &szPath))
			{
				string texture_file_name=stringhelper::remove_extension(stringhelper::get_filename(stringhelper::fixup(szPath.data)));
				rendertexture_t* texture=rendersystem_t::get_texture(stringhash::get(act_path+texture_file_name));
				submesh.textures.push_back(texture);
			}

			if(AI_SUCCESS == aiGetMaterialString(mat, AI_MATKEY_TEXTURE_REFLECTION(0), &szPath))
			{
				string texture_file_name=stringhelper::remove_extension(stringhelper::get_filename(stringhelper::fixup(szPath.data)));
				rendertexture_t* texture=rendersystem_t::get_texture(stringhash::get(act_path+texture_file_name));
				submesh.textures.push_back(texture);
			}
		}
	}





	for (uint32 n=0; n<ainode->mNumChildren;++n)
	{
//		rendernode_t* c=node->add_child();
		int i=load_assimp_node(scene,ainode->mChildren[n],nodebuf);
		nodebuf[i].parent_index=index;
	}

	return index;
}
#endif

